<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>

<body>
    <article>
        <h1 id="headline">你好,解密者</h1>
        <p id="text">
            馨魂原是水花香
            雪峰於此谢灵光
            我有清风高节在
            爱山必欲斯民爱
            林卿妙龄才秀发
            馨与天游身世外
            雪霜贸贸年芳改
            林泉有分酬清供
            馨欣乔木岁时荣
        </p>
    </article>
</body>
<style>
    * {
        box-sizing: border-box;
    }

    html,
    body {
        width: 100%;
        height: 100%;
    }

    body {
        margin: 0;
        overflow: hidden;
        background: #222;
        font-family: monospace;
    }

    h1 {
        margin-top: 0;
    }

    button {
        font-family: monospace;
        border: 2px solid tomato;
        background: transparent;
        width: 250px;
        font-size: 1.2em;
        padding: 10px 0;
        border-radius: 5px;
        display: block;
        cursor: pointer;
        margin: 1em auto;
    }

    article {
        width: 80%;
        margin: auto;
        font-size: 2em;
        top: 50%;
        position: relative;
        transform: translateY(-50%);
    }
</style>
<script>
    function WordShuffler(holder, opt) {
        var that = this;
        var time = 0;
        this.now;
        this.then = Date.now();

        this.delta;
        this.currentTimeOffset = 0;

        this.word = null;
        this.currentWord = null;
        this.currentCharacter = 0;
        this.currentWordLength = 0;


        var options = {
            fps: 20,
            timeOffset: 5,
            textColor: '#000',
            fontSize: "50px",
            useCanvas: false,
            mixCapital: false,
            mixSpecialCharacters: false,
            needUpdate: true,
            colors: [
                '#f44336', '#e91e63', '#9c27b0',
                '#673ab7', '#3f51b5', '#2196f3',
                '#03a9f4', '#00bcd4', '#009688',
                '#4caf50', '#8bc34a', '#cddc39',
                '#ffeb3b', '#ffc107', '#ff9800',
                '#ff5722', '#795548', '#9e9e9e',
                '#607d8b'
            ]
        }

        if (typeof opt != "undefined") {
            for (key in opt) {
                options[key] = opt[key];
            }
        }



        this.needUpdate = true;
        this.fps = options.fps;
        this.interval = 1000 / this.fps;
        this.timeOffset = options.timeOffset;
        this.textColor = options.textColor;
        this.fontSize = options.fontSize;
        this.mixCapital = options.mixCapital;
        this.mixSpecialCharacters = options.mixSpecialCharacters;
        this.colors = options.colors;

        this.useCanvas = options.useCanvas;

        this.chars = [
            'A', 'B', 'C', 'D',
            'E', 'F', 'G', 'H',
            'I', 'J', 'K', 'L',
            'M', 'N', 'O', 'P',
            'Q', 'R', 'S', 'T',
            'U', 'V', 'W', 'X',
            'Y', 'Z'
        ];
        this.specialCharacters = [
            '!', '§', '$', '%',
            '&', '/', '(', ')',
            '=', '?', '_', '<',
            '>', '^', '°', '*',
            '#', '-', ':', ';', '~'
        ]

        if (this.mixSpecialCharacters) {
            this.chars = this.chars.concat(this.specialCharacters);
        }

        this.getRandomColor = function () {
            var randNum = Math.floor(Math.random() * this.colors.length);
            return this.colors[randNum];
        }

        //if Canvas

        this.position = {
            x: 0,
            y: 50
        }

        //if DOM
        if (typeof holder != "undefined") {
            this.holder = holder;
        }

        if (!this.useCanvas && typeof this.holder == "undefined") {
            console.warn('Holder must be defined in DOM Mode. Use Canvas or define Holder');
        }


        this.getRandCharacter = function (characterToReplace) {
            if (characterToReplace == " ") {
                return ' ';
            }
            var randNum = Math.floor(Math.random() * this.chars.length);
            var lowChoice = -.5 + Math.random();
            var picketCharacter = this.chars[randNum];
            var choosen = picketCharacter.toLowerCase();
            if (this.mixCapital) {
                choosen = lowChoice < 0 ? picketCharacter.toLowerCase() : picketCharacter;
            }
            return choosen;

        }

        this.writeWord = function (word) {
            this.word = word;
            this.currentWord = word.split('');
            this.currentWordLength = this.currentWord.length;

        }

        this.generateSingleCharacter = function (color, character) {
            var span = document.createElement('span');
            span.style.color = color;
            span.innerHTML = character;
            return span;
        }

        this.updateCharacter = function (time) {

            this.now = Date.now();
            this.delta = this.now - this.then;



            if (this.delta > this.interval) {
                this.currentTimeOffset++;

                var word = [];

                if (this.currentTimeOffset === this.timeOffset && this.currentCharacter !== this.currentWordLength) {
                    this.currentCharacter++;
                    this.currentTimeOffset = 0;
                }
                for (var k = 0; k < this.currentCharacter; k++) {
                    word.push(this.currentWord[k]);
                }

                for (var i = 0; i < this.currentWordLength - this.currentCharacter; i++) {
                    word.push(this.getRandCharacter(this.currentWord[this.currentCharacter + i]));
                }


                if (that.useCanvas) {
                    c.clearRect(0, 0, stage.x * stage.dpr, stage.y * stage.dpr);
                    c.font = that.fontSize + " sans-serif";
                    var spacing = 0;
                    word.forEach(function (w, index) {
                        if (index > that.currentCharacter) {
                            c.fillStyle = that.getRandomColor();
                        } else {
                            c.fillStyle = that.textColor;
                        }
                        c.fillText(w, that.position.x + spacing, that.position.y);
                        spacing += c.measureText(w).width;
                    });
                } else {

                    if (that.currentCharacter === that.currentWordLength) {
                        that.needUpdate = false;
                    }
                    this.holder.innerHTML = '';
                    word.forEach(function (w, index) {
                        var color = null
                        if (index > that.currentCharacter) {
                            color = that.getRandomColor();
                        } else {
                            color = that.textColor;
                        }
                        that.holder.appendChild(that.generateSingleCharacter(color, w));
                    });
                }
                this.then = this.now - (this.delta % this.interval);
            }
        }

        this.restart = function () {
            this.currentCharacter = 0;
            this.needUpdate = true;
        }

        function update(time) {
            time++;
            if (that.needUpdate) {
                that.updateCharacter(time);
            }
            requestAnimationFrame(update);
        }

        this.writeWord(this.holder.innerHTML);


        console.log(this.currentWord);
        update(time);
    }




    var headline = document.getElementById('headline');
    var text = document.getElementById('text');
    var shuffler = document.getElementById('shuffler');

    var headText = new WordShuffler(headline, {
        textColor: '#fff',
        timeOffset: 18,
        mixCapital: true,
        mixSpecialCharacters: true
    });

    var pText = new WordShuffler(text, {
        textColor: '#fff',
        timeOffset: 2
    });

    var buttonText = new WordShuffler(shuffler, {
        textColor: 'tomato',
        timeOffset: 10
    });



    shuffler.addEventListener('click', function () {
        headText.restart();
        pText.restart();
        buttonText.restart();
    });

</script>

</html>